<?php

namespace PhpBoot\ORM;

use PhpBoot\DB\DB;

class ModelWithObject
{
    /**
     * @var bool
     */
    private $useSub;

    /**
     * ModelWithObject constructor.
     * @param DB $db
     * @param object $entity
     */
    public function __construct(DB $db, $entity)
    {
        is_object($entity) or \PhpBoot\abort(new \InvalidArgumentException('object required'));
        $entityName = get_class($entity);
        $this->db = $db;
        $builder = $db->getApp()->get(ModelContainerBuilder::class);
        $this->entity = $builder->build($entityName);
        $this->object = $entity;
        $this->useSub = $this->entity->getSubKey() && $this->entity->getSubTable();
    }

    /**
     * @return void
     */
    public function unSub()
    {
        $this->useSub = false;
    }
    /**
     * @return void
     */
    public function create()
    {
        $data = $this->getCreateData();
        $this->db->transaction(function (DB $db) use ($data) {
            $id = $db->insertInto($this->entity->getTable())
                ->values($data)
                ->exec()->lastInsertId();
            $this->object->{$this->entity->getPK()} = $id;
            if ($this->useSub) {
                $subData = $this->getCreateData(true);
                $subData[$this->entity->getSubKey()] = $id;
                $db->insertInto($this->entity->getSubTable())
                    ->values($subData)
                    ->exec();
            }
            return $id;
        });
    }
    private function getCreateData($sub = false)
    {
        $data = [];
        foreach ($this->getColumns($sub) as $column){
            if(isset($this->object->$column)){
                if(is_array($this->object->$column) || is_object($this->object->$column)){
                    $data[$column] = json_encode($this->object->$column);
                }else{
                    $data[$column] = $this->object->$column;
                }
            }
        }
        return $data;
    }

    /**
     * @param array $columns columns to update. if columns is empty array, update all of the columns
     * @return int rows updated
     */
    public function update(array $columns=[])
    {
        $pk = $this->entity->getPK();
        $data = $this->getUpdateData($columns, $pk);

        return $this->db->transaction(function (DB $db) use ($data, $pk, $columns) {
            $rows = $db->update($this->entity->getTable())
                ->set($data)
                ->where("`{$pk}` = ?", $this->object->$pk)
                ->exec()->rows;
            if ($this->useSub) {
                $subData = $this->getUpdateData($columns, $pk, true);
                $db->update($this->entity->getSubTable())
                    ->set($subData)
                    ->where("`{$this->entity->getSubKey()}` = ?", $this->object->$pk)
                    ->exec();
            }
            return $rows;
        });
    }
    private function getUpdateData($columns, $pk, $sub = false)
    {
        $data = [];
        foreach ($this->getColumns($sub) as $column){
            if(count($columns) && !in_array($column, $columns)){
                continue;
            }
            if($pk != $column && isset($this->object->$column)){
                if(is_array($this->object->$column) || is_object($this->object->$column)){
                    $data[$column] = json_encode($this->object->$column);
                }else{
                    $data[$column] = $this->object->$column;
                }
            }
        }
        return $data;
    }

    /**
     * @return int rows deleted
     */
    public function delete()
    {
        return $this->db->transaction(function (DB $db) {
            $pk = $this->entity->getPK();
            $rows = $db->deleteFrom($this->entity->getTable())
                ->where([$pk => $this->object->$pk])
                ->exec()->rows;
            if ($this->useSub) {
                $db->deleteFrom($this->entity->getSubTable())
                    ->where([$this->entity->getSubKey() => $this->object->$pk])
                    ->exec();
            }
            return $rows;
        });
    }

    /**
     * set entity table name
     * @param string $tableName
     * @return $this
     */
    public function withTable($tableName)
    {
        $this->entity->setTable($tableName);
        return $this;
    }

    protected function getColumns($sub = false)
    {
        $columns = [];
        foreach ($this->entity->getProperties() as $p){
            if ($p->isSub == $sub) {
                $columns[] = $p->name;
            }
        }
        return $columns;
    }

    /**
     * @var object
     */
    protected $object;

    /**
     * @var ModelContainer
     */
    protected $entity;
    /**
     * @var DB
     */
    protected $db;
}